/**************************************************************************************
*
*	Copyright (C) 2009 - 2012 Brent Andrew O. Chua - See LICENSE.txt for more info.
*		
*	Filename	-	WindowView.cpp
*
*	Comments	-	See WindowView.h
*
**************************************************************************************/

#include "../Include/WindowView.h"
#include "../Include/Engine.h"
#include "../Include/Window.h"
#include "../Include/GraphicsResourceManager.h"
#include "../Include/CameraRenderPathSettingsLPP.h"
#include "../Include/Scene.h"
#include "../Include/RenderEntryWindowViewPresent.h"
#include "../Include/RenderEntryBindParameters.h"
#include "../Include/RenderEntryViewport.h"
#include "../Include/GeometryGenerator.h"
#include "../Include/GlobalGraphicsStrings.h"
#include "../Include/ComponentCamera.h"
#include "../Include/ParameterBinderScalar.h"

#include "../Include/EventWindowViewResize.h"
#include "../Include/EventWindowViewDestroy.h"

namespace Pulse
{

	void WindowView::Update( const FLOAT dt )
	{
		// TODO: Add an update frequency
		m_eventSystem.ProcessEvents();
		m_pScene->Update( dt );
	}

	void WindowView::Render( void )
	{
		//Engine::GetInstance()->GetRenderer()->GetRenderPath()->SetActiveRenderPathViewSettings( m_pWVRPSettings );
		m_pScene->Render();
	}

	void WindowView::OnSceneRender( RenderView *pView )
	{
		pView->AddRenderEntry( m_pRESceneParams );
	}

	void WindowView::RenderToWindow( RenderView *pView )
	{
		pView->AddRenderEntry( m_pREViewport );
		pView->AddRenderEntry( m_pREWVPresent );
	}

	//Texture2D * WindowView::GetRenderTexture( void )
	//{
	//	if ( m_pRenderTexture )
	//	{
	//		m_pRenderTexture->AddRef();
	//		return m_pRenderTexture;
	//	}

	//	return PSX_NULL;
	//}

	Texture2D * WindowView::GetRenderTexture( void )
	{
		m_pRenderTexture->AddRef();

		return m_pRenderTexture; 
	}


	EErrorCode::Type WindowView::Create( const CHAR *pViewName, WindowView **ppOut )
	{
		if ( ppOut == PSX_NULL )
		{
			PSX_PushError( "ppOut must be null." );
			return EErrorCode::INVALID_PARAMETER;
		}

		if ( pViewName == PSX_NULL )
		{
			PSX_PushError( "Must have a valid WindowView name." );
			return EErrorCode::INVALID_PARAMETER;
		}

		WindowView *pNewView = new WindowView( pViewName );

		if ( pNewView == PSX_NULL )
		{
			PSX_PushError( "Out of memory." );
			return EErrorCode::MEMORY;
		}

		EErrorCode::Type retCode = pNewView->Initialize();

		if ( retCode != EErrorCode::OKAY )
		{
			return retCode;
		}

		*ppOut = pNewView;

		return EErrorCode::OKAY;
	}

	void WindowView::Delete( WindowView *pView )
	{
		// pView->Cleanup(); // Implicitly called
		delete pView;
	}

	WindowView::WindowView( const CHAR *pViewName )
	{
		m_ratioPosX = 0;
		m_ratioPosY = 0;
		m_ratioWidth = 0;
		m_ratioHeight = 0;
		m_posX = 0;
		m_posY = 0;
		m_width = 0;
		m_height = 0;

		m_pWindowOwner = PSX_NULL;
		m_pScene = PSX_NULL;

		m_pRESceneParams = PSX_NULL;

		m_pREViewport = PSX_NULL;
		m_pREWVPresent = PSX_NULL;
		m_pRenderTexture = PSX_NULL;
		m_pMaterial = PSX_NULL;
		m_pGeometry = PSX_NULL;
		m_pModel = PSX_NULL;
	}

	WindowView::~WindowView( void)
	{
		Cleanup();
	}

	EErrorCode::Type WindowView::Initialize( void )
	{
		EErrorCode::Type retCode;
		RenderPathForwardPlus *pRenderPath = Engine::GetInstance()->GetRenderer()->GetRenderPath();

		if ( m_pScene != PSX_NULL )
			return EErrorCode::OKAY;

		m_pScene = new Scene;

		if( m_pScene == PSX_NULL )
		{
			PSX_PushError( "Out of memory." );
			return EErrorCode::MEMORY;
		}

		retCode = m_pScene->Initialize( this );

		if ( retCode != EErrorCode::OKAY )
		{
			return retCode;
		}

		m_pRESceneParams = new RenderEntryBindParameters;

		if ( m_pRESceneParams == PSX_NULL )
		{
			return EErrorCode::MEMORY;
		}

		m_pRESceneParams->SetStage( ERenderViewStage::MAIN );
		m_pRESceneParams->SetStageStep( ERenderViewStageStep::PRE_PROCESS );

		m_pREViewport = new RenderEntryViewport;
		m_pREWVPresent = new RenderEntryWindowViewPresent;

		if ( m_pREViewport == PSX_NULL || m_pREWVPresent  == PSX_NULL )
		{
			PSX_PushError( "Out of memory." );

			return EErrorCode::MEMORY;
		}

		m_pREWVPresent->SetStage( ERenderViewStage::WINDOW_VIEW );
		m_pREWVPresent->SetStageStep( ERenderViewStageStep::PROCESS );

		m_pREViewport->SetStage( ERenderViewStage::WINDOW_VIEW );
		m_pREViewport->SetStageStep( ERenderViewStageStep::PRE_PROCESS );

		return OnInitialize();
	}

	void WindowView::Cleanup( void )
	{
		// Inform all the cameras in the scene that we're about to be destroyed...
		{
			EventWindowViewDestroy event;
			TriggerEvent( &event );
		}

		OnCleanup();

		if ( m_pScene )
		{
			PSX_SafeDelete( m_pScene );
		}

		PSX_SafeDelete( m_pREViewport );
		PSX_SafeDelete( m_pREWVPresent );

		ReleaseWindowViewRenderData();

		//RenderPathLightPrePass *pRenderPath = Engine::GetInstance()->GetRenderer()->GetRenderPath();

		//if ( m_pWVRPSettings )
		//{
		//	pRenderPath->RemoveWindowViewRenderPathSettings( this, m_pWVRPSettings );
		//	m_pWVRPSettings = PSX_NULL;
		//}
		//ReleaseRenderTexture();

		SetOwner( PSX_NULL );
	}

	void WindowView::SetOwner( Window *pOwner )
	{ 

		// Unregister events
		if ( m_pWindowOwner != PSX_NULL )
		{
			EventSystem *pEventSys = this->GetOwner()->GetEventSystem();

			pEventSys->RemoveListener( EEvent::INPUT_KEY_UP, this, &WindowView::OnKeyUp );
			pEventSys->RemoveListener( EEvent::INPUT_KEY_DOWN, this, &WindowView::OnKeyDown );
			pEventSys->RemoveListener( EEvent::INPUT_MOUSE_MOVE, this, &WindowView::OnMouseMove );
			pEventSys->RemoveListener( EEvent::INPUT_MOUSE_BUTTONUP, this, &WindowView::OnMouseButtonUp );
			pEventSys->RemoveListener( EEvent::INPUT_MOUSE_BUTTONDOWN, this, &WindowView::OnMouseButtonDown );
		}

		m_pWindowOwner = pOwner; 

		// Register events
		if ( m_pWindowOwner != PSX_NULL )
		{
			EventSystem *pEventSys = this->GetOwner()->GetEventSystem();

			pEventSys->AddListener( EEvent::INPUT_KEY_UP, this, &WindowView::OnKeyUp );
			pEventSys->AddListener( EEvent::INPUT_KEY_DOWN, this, &WindowView::OnKeyDown );
			pEventSys->AddListener( EEvent::INPUT_MOUSE_MOVE, this, &WindowView::OnMouseMove );
			pEventSys->AddListener( EEvent::INPUT_MOUSE_BUTTONUP, this, &WindowView::OnMouseButtonUp );
			pEventSys->AddListener( EEvent::INPUT_MOUSE_BUTTONDOWN, this, &WindowView::OnMouseButtonDown );
		}
	}

	EErrorCode::Type WindowView::ResetOrientation(	const FLOAT ratioX, const FLOAT ratioY, 
			const FLOAT ratioWidth, const FLOAT ratioHeight )
	{
		Window *pOwner = GetOwner();
		SIZE_T ownerWidth = pOwner->GetWidth();
		SIZE_T ownerHeight = pOwner->GetHeight();
		EErrorCode::Type retCode;

		// TODO: We'll also need to add anchor functionality
		m_ratioPosX = ratioX;
		m_ratioPosY = ratioY;
		m_ratioWidth = ratioWidth;
		m_ratioHeight = ratioHeight;

		// Calculate new size
		m_posX		=	(SIZE_T)(ownerWidth	* ratioX);
		m_posY		=	(SIZE_T)(ownerHeight * ratioY);
		m_width		=	(SIZE_T)(ownerWidth	* ratioWidth);
		m_height	=	(SIZE_T)(ownerHeight * ratioHeight);

		//m_pWVRPSettings->ReleaseTextureBuffers();
		//retCode = m_pWVRPSettings->UpdateBufferSizes();

		//if ( retCode != EErrorCode::OKAY )
		//{
		//	return retCode;
		//}

		retCode = UpdateRenderInfo();

		if ( retCode != EErrorCode::OKAY )
		{
			return retCode;
		}

		// Update active camera's view
		ActorPtr pCam = m_pScene->GetCamera();
		if ( pCam.IsValid() )
		{
			ComponentCamera *pCamComp = (ComponentCamera*)pCam->GetComponent( EComponent::CAMERA );
			FLOAT aspect = (FLOAT)m_width / (FLOAT)m_height;
			pCamComp->SetProjectionPerspective( 0.01f, 1000.0f, aspect, PSX_PI * 0.25f );
		}

		// Update viewport
		{
			Viewport viewport;

			viewport.Set( (FLOAT)m_posX, (FLOAT)m_posY, (FLOAT)m_width, (FLOAT)m_height );
			m_pREViewport->SetViewport( &viewport );
		}

		// Inform all the cameras in the scene...
		{
			EventWindowViewResize event;
			TriggerEvent( &event );
		}

		return EErrorCode::OKAY;
	}

	EErrorCode::Type WindowView::ResetOrientation( void )
	{
		return ResetOrientation( m_ratioPosX, m_ratioPosY, m_ratioWidth, m_ratioHeight );
	}

	EErrorCode::Type WindowView::UpdateRenderInfo( void )
	{
		Renderer *pRenderer = Engine::GetInstance()->GetRenderer();
		RenderPathForwardPlus *pRenderPath = pRenderer->GetRenderPath();
		ParameterSystem *pParamSys = pRenderer->GetParameterSystem();
		GraphicsResourceManager *pGM = GraphicsResourceManager::GetInstance();
		Window *pOwner = GetOwner();

		ReleaseWindowViewRenderData();

		// Recreate buffer
		{
			GraphicsResourceManager *pGM = GraphicsResourceManager::GetInstance();
			Texture2DDesc desc;
			
			PSX_SafeRelease( m_pRenderTexture );

			desc.SetAsRenderTarget( (UINT)m_width, (UINT)m_height );

			m_pRenderTexture = pGM->CreateTexture2D( &desc );

			if ( m_pRenderTexture == PSX_NULL )
			{
				PSX_PushError( "Failed to create WindowView rendertarget." );
				return EErrorCode::_ERROR;
			}
		}

		// Create Model with Material and Geomtry
		//////////////////////////////////////////

		// Material
		MaterialDesc matDesc;

		matDesc.SetToLoad( PSX_String("WindowView.mtl") );
		m_pMaterial = pGM->CreateMaterial( &matDesc );

		if ( m_pMaterial == PSX_NULL )
		{
			PSX_PushError( "Failed to create WindowView render info material." );
			return EErrorCode::_ERROR;
		}

		
		// Set Render entry parameter info for the scene
		{
			ParameterScalar *pParam;
			ParameterBinderScalar *pBinder;

			// Remove any existing/old parameter binders
			m_pRESceneParams->ClearParameters();

			pParam = pParamSys->GetScalar( GFX_PARAM_WINDOW_VIEW_WIDTH ); // UINT
			pBinder = (ParameterBinderScalar*)m_pRESceneParams->AddParameter( pParam );
			pBinder->SetAsScalar( (UINT)m_width );

			pParam = pParamSys->GetScalar( GFX_PARAM_WINDOW_VIEW_HEIGHT ); // UINT
			pBinder = (ParameterBinderScalar*)m_pRESceneParams->AddParameter( pParam );
			pBinder->SetAsScalar( (UINT)m_height );
		}

		// This is kind of a hack manually setting one of the requirements
		ParameterContainer *pMatParams = m_pMaterial->GetMaterialParameters();
		IParameterBinder *pParam = pMatParams->AddParameterBinder( EParameter::TEXTURE2D, GFX_PARAM_TEXTURE_WINDOW_VIEW );
		//pParam->SetAsShaderResource( m_pWVRPSettings->GetCompositeTexture() );
		pParam->SetAsShaderResource( m_pRenderTexture );

		// Geometry
		GeometryDesc geoDesc;
		geoDesc.AddVertexSourceType( EVertexElement::POSITION, 4 );
		geoDesc.AddVertexSourceType( EVertexElement::TEXTURE_DIFFUSE, 4 );
		geoDesc.AddIndexSource( 6 );

		Vector3	*pVertices = geoDesc.GetVertexSource( EVertexElement::POSITION )->GetAsPositions();
		Vector2	*pVertDiffuse = geoDesc.GetVertexSource( EVertexElement::TEXTURE_DIFFUSE )->GetAsTextureDiffuse();
		UINT16 *pIndices = geoDesc.GetIndexSource()->GetAs16BitIndices();

		// Set to unit scale quad
		pVertices[0].Set( -1.0f, -1.0f, 0.0f );	pVertDiffuse[0].Set( 0.0f, 1.0f );
		pVertices[1].Set( -1.0f, 1.0f, 0.0f );	pVertDiffuse[1].Set( 0.0f, 0.0f );
		pVertices[2].Set( 1.0f, 1.0f, 0.0f );	pVertDiffuse[2].Set( 1.0f, 0.0f );
		pVertices[3].Set( 1.0f, -1.0f, 0.0f );	pVertDiffuse[3].Set( 1.0f, 1.0f );

		// calculate final ndc by multiplying world, proj matrices
		Matrix4x4 matWorld, matProj, matWorldProj;

		matWorld.BuildTranslation( (FLOAT)m_posX, (FLOAT)m_posY, 0.0f );
		matProj.BuildOrthogonalProjection( pOwner->GetWidth(), pOwner->GetHeight(), 0.01f, 1000.0f );
		matWorldProj = matWorld * matProj;

		for ( SIZE_T i = 0; i < 4; ++i )
		{
			Vector3 *pVertex = &pVertices[i];
			
			pVertex->x *= (m_width * 0.5f);
			pVertex->y *= (m_height * 0.5f);

			*pVertex = *pVertex * matWorldProj;
		}

		// Set indices
		pIndices[0] = 0;
		pIndices[1] = 1;
		pIndices[2] = 2;

		pIndices[3] = 0;
		pIndices[4] = 2;
		pIndices[5] = 3;

		geoDesc.AddGeometrySubset( 2 );

		m_pGeometry = pGM->CreateGeometry( &geoDesc );

		if ( m_pGeometry == PSX_NULL )
		{
			PSX_PushError( "Failed to create geometry." );
			return EErrorCode::_ERROR;
		}

		ModelDesc modelDesc;
		
		modelDesc.SetManual( m_pMaterial, m_pGeometry );
		m_pModel = pGM->CreateModel( &modelDesc );
		m_pREWVPresent->SetModel( m_pModel );

		// Generate render entry list
		EErrorCode::Type retCode = pRenderPath->BuildRenderEntries( m_pModel, m_pREWVPresent->GetRenderEntryList() );

		if ( retCode != EErrorCode::OKAY )
		{
			return retCode;
		}

		return EErrorCode::OKAY;
	}

	void WindowView::ReleaseWindowViewRenderData( void )
	{
		PSX_SafeRelease( m_pRenderTexture );
		PSX_SafeRelease( m_pModel );
		PSX_SafeRelease( m_pGeometry );
		PSX_SafeRelease( m_pMaterial );
	}

	//void WindowView::ReleaseRenderTexture( void )
	//{
	//	PSX_SafeRelease( m_pRenderTexture );
	//}

	//EErrorCode::Type WindowView::RecreateRenderTexture( const SIZE_T width, const SIZE_T height )
	//{
	//	ReleaseRenderTexture();

	//	Texture2DDesc desc;

	//	desc.SetAsRenderTarget( (UINT)width, (UINT)height );

	//	m_pRenderTexture = GraphicsResourceManager::GetInstance()->CreateTexture2D( &desc );

	//	if ( m_pRenderTexture == PSX_NULL )
	//	{
	//		PSX_PushError( "Failed to create render texture." );
	//		return EErrorCode::GRAPHICS;
	//	}

	//	return EErrorCode::OKAY;
	//}

	void WindowView::OnKeyUp( IEvent *pEvent )
	{
		//EventKeyUp *pDerivedEvent = (EventKeyUp*)pEvent;
		m_eventSystem.PostEventUnmanaged( pEvent );

	}

	void WindowView::OnKeyDown( IEvent *pEvent )
	{
		//EventKeyDown *pDerivedEvent = (EventKeyDown*)pEvent;
		m_eventSystem.PostEventUnmanaged( pEvent );

	}

	void WindowView::OnMouseMove( IEvent *pEvent )
	{
		//EventMouseMove *pDerivedEvent = (EventMouseMove*)pEvent;
		m_eventSystem.PostEventUnmanaged( pEvent );

	}

	void WindowView::OnMouseButtonUp( IEvent *pEvent )
	{
		//EventMouseButtonUp *pDerivedEvent = (EventMouseButtonUp*)pEvent;
		m_eventSystem.PostEventUnmanaged( pEvent );

	}

	void WindowView::OnMouseButtonDown( IEvent *pEvent )
	{
		//EventMouseButtonDown *pDerivedEvent = (EventMouseButtonDown*)pEvent;
		m_eventSystem.PostEventUnmanaged( pEvent );
	}
}